package com.gitee.wsl.time.wheel


import assertk.Assert
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isLessThan
import assertk.assertions.isLessThanOrEqualTo
import assertk.assertions.isNotNull
import assertk.assertions.isNotSameInstanceAs
import assertk.assertions.isNull
import assertk.assertions.isSameInstanceAs
import assertk.assertions.isTrue
import com.gitee.wsl.time.wheel.TimerWheel.Companion.SHIFT
import com.gitee.wsl.time.wheel.TimerWheel.Companion.SPANS
import com.gitee.wsl.time.wheel.TimerWheel.Sentinel
import com.gitee.wsl.time.TimeUnit
import com.gitee.wsl.time.ofDays
import com.gitee.wsl.time.ofSeconds
import com.gitee.wsl.time.toNanos
import com.gitee.wsl.time.toSeconds
import com.gitee.wsl.transform.validator.annotation.hasSize
import org.jetbrains.annotations.Nullable
import org.junit.Assert.assertThrows
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import java.lang.String
import java.util.TreeMap
import java.util.concurrent.ThreadLocalRandom
import java.util.function.LongFunction
import java.util.stream.LongStream
import kotlin.Any
import kotlin.Array
import kotlin.AssertionError
import kotlin.Boolean
import kotlin.IllegalArgumentException
import kotlin.Int
import kotlin.Long
import kotlin.LongArray
import kotlin.NoSuchElementException
import kotlin.UnsupportedOperationException
import kotlin.arrayOf
import kotlin.collections.ArrayList
import kotlin.collections.Iterable
import kotlin.collections.MutableIterator
import kotlin.collections.MutableList
import kotlin.collections.indices
import kotlin.intArrayOf
import kotlin.longArrayOf
import kotlin.test.Test
import kotlin.text.StringBuilder
import kotlin.time.Duration



/**
 * @author ben.manes@gmail.com (Ben Manes)
 */
class TimerWheelTest {
    // Use printTimerWheel(timerWheel) to debug
    @Test/*(dataProvider = "schedule")*/
    fun scheduleTest(){
        providesSchedule().forEach { schedule(it[0],it[1],it[2].toInt())}
    }

    fun schedule(clock: Long, duration: Long, expired: Int) {
        val captor = ArgumentCaptor.captor<Node<Long, Long>>()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true)

        timerWheel.nanos = clock
        for (timeout in intArrayOf(25, 90, 240)) {
            timerWheel.schedule(Timer(clock + TimeUnit.SECONDS.toNanos(timeout)))
        }
        timerWheel.advance(cache, clock + duration)
        verify(cache, times(expired)).evictEntry(any(), any(), anyLong())

        for (node in captor.getAllValues()) {
            assertThat(node.variableTime).isAtMost(clock + duration)
        }
    }

    //@Test(dataProvider = "fuzzySchedule")
    fun schedule_fuzzy(clock: Long, duration: Long, times: LongArray) {
        val captor: ArgumentCaptor<Node<Long, Long>> = ArgumentCaptor.captor()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true)
        timerWheel.nanos = clock

        var expired = 0
        for (timeout in times) {
            if (timeout <= duration) {
                expired++
            }
            timerWheel.schedule(Timer(timeout))
        }
        timerWheel.advance(cache, duration)
        verify(cache, times(expired)).evictEntry(any(), any(), anyLong())

        for (node in captor.getAllValues()) {
            assertThat(node.variableTime).isAtMost(duration)
        }
        checkTimerWheel(timerWheel, duration)
    }

    @Test
    fun findBucket_expired() {
        val timerWheel = TimerWheel<Long, Long>()
        val clock = ThreadLocalRandom.current().nextLong()
        val duration = ThreadLocalRandom.current().nextLong(Long.Companion.MIN_VALUE, 0)

        timerWheel.nanos = clock
        val expected = timerWheel.findBucket(clock)
        val bucket = timerWheel.findBucket(clock + duration)
        assertThat(bucket).isSameInstanceAs(expected)
    }

    //@Test(dataProvider = "clock")
    fun advance(clock: Long) {
        val captor: ArgumentCaptor<Node<Long, Long>> = ArgumentCaptor.captor()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true)

        timerWheel.nanos = clock
        timerWheel.schedule(Timer(timerWheel.nanos + SPANS[0]))

        timerWheel.advance(cache, clock + 13 * SPANS[0])
        verify(cache).evictEntry(any(), any(), anyLong())
    }

    @Test
    fun advance_overflow() {
        val captor: ArgumentCaptor<Node<Long, Long>> = ArgumentCaptor.captor()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true)

        timerWheel.nanos = -TimeUnit.DAYS.toNanos(365) / 2
        timerWheel.schedule(Timer(timerWheel.nanos + SPANS[0]))

        timerWheel.advance(cache, timerWheel.nanos + TimeUnit.DAYS.toNanos(365))
        verify(cache).evictEntry(any(), any(), anyLong())
    }

    //@Test(dataProvider = "clock")
    fun advance_backwards(clock: Long) {
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        timerWheel.nanos = clock
        for (i in 0..1000 - 1) {
            val duration = ThreadLocalRandom.current().nextLong(TimeUnit.DAYS.toNanos(10))
            timerWheel.schedule(Timer(clock + duration))
        }
        for (i in TimerWheel.BUCKETS.indices) {
            timerWheel.advance(cache, clock - 3 * SPANS[i])
        }

        verifyNoInteractions(cache)
    }

    //@Test(dataProvider = "clock")
    fun advance_reschedule(clock: Long) {
        val captor: ArgumentCaptor<Node<Long, Long>> = ArgumentCaptor.captor()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true)
        timerWheel.nanos = clock

        val t15 = Timer(clock + TimeUnit.SECONDS.toNanos(15))
        val t80 = Timer(clock + TimeUnit.SECONDS.toNanos(80))
        timerWheel.schedule(t15)
        timerWheel.schedule(t80)

        // discard T15, T80 in wheel[1]
        timerWheel.advance(cache, clock + TimeUnit.SECONDS.toNanos(45))
        assertThat(captor.getAllValues()).containsExactly(t15)
        assertThat(timerWheel).hasSize(1)

        // verify not discarded, T80 in wheel[0]
        timerWheel.advance(cache, clock + TimeUnit.SECONDS.toNanos(70))
        assertThat(timerWheel).hasSize(1)

        // verify discarded T80
        timerWheel.advance(cache, clock + TimeUnit.SECONDS.toNanos(90))
        assertThat(captor.getAllValues()).containsExactly(t15, t80)
        assertThat(timerWheel).isEmpty()
    }

    @Test
    fun advance_exception() {
        val captor: ArgumentCaptor<Node<Long, Long>> = ArgumentCaptor.captor()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        doThrow(IllegalArgumentException())
            .`when`(cache).evictEntry(captor.capture(), any(), anyLong())
        val timer = Timer(timerWheel.nanos + SPANS[1])

        timerWheel.nanos = 0L
        timerWheel.schedule(timer)
        assertThrows(IllegalArgumentException::class.java, { timerWheel.advance(cache, Long.Companion.MAX_VALUE) })
        assertThat(timerWheel.wheel[1][1].nextInVariableOrder).isSameInstanceAs(timer)
        assertThat(timerWheel.nanos).isEqualTo(0)
    }

    //@Test(dataProvider = "clock")
    fun getExpirationDelay_empty(clock: Long) {
        val timerWheel = TimerWheel<Long, Long>()

        timerWheel.nanos = clock
        assertThat(timerWheel.expirationDelay).isAtMost(Long.Companion.MAX_VALUE)
    }

    //@Test(dataProvider = "clock")
    fun getExpirationDelay_firstWheel(clock: Long) {
        val timerWheel = TimerWheel<Long, Long>()

        timerWheel.nanos = clock
        val delay: Long = Duration.ofSeconds(1).toNanos()
        timerWheel.schedule(Timer(clock + delay))
        assertThat(timerWheel.expirationDelay).isAtMost(SPANS[0])
    }

    //@Test(dataProvider = "clock")
    fun getExpirationDelay_lastWheel(clock: Long) {
        val timerWheel = TimerWheel<Long, Long>()

        timerWheel.nanos = clock
        val delay: Long = Duration.ofDays(14).toNanos()
        timerWheel.schedule(Timer(clock + delay))
        assertThat(timerWheel.expirationDelay).isAtMost(delay)
    }

    //@Test(dataProvider = "clock")
    fun getExpirationDelay_hierarchy(clock: Long) {
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(any(), any(), anyLong())).thenReturn(true)
        timerWheel.nanos = clock

        val t15: Long = clock + Duration.ofSeconds(15).toNanos() // in wheel[0]
        val t80: Long = clock + Duration.ofSeconds(80).toNanos() // in wheel[1]
        timerWheel.schedule(Timer(t15))
        timerWheel.schedule(Timer(t80))

        val t45: Long = clock + Duration.ofSeconds(45).toNanos() // discard T15, T80 in wheel[1]
        timerWheel.advance(cache, t45)

        val t95: Long = clock + Duration.ofSeconds(95).toNanos() // in wheel[0], but expires after T80
        timerWheel.schedule(Timer(t95))

        val expectedDelay = (t80 - t45)
        val delay = timerWheel.expirationDelay
        assertThat(delay).isLessThan(expectedDelay + SPANS[0]) // cascaded T80 in wheel[1]
    }

//    @Test(dataProvider = "fuzzySchedule", invocationCount = 25)
//    fun getExpirationDelay_fuzzy(clock: Long, duration: Long, times: LongArray) {
//        val cache: BoundedAble<Long, Long> = Mockito.mock()
//        val timerWheel = TimerWheel<Long, Long>()
//
//        `when`(cache.evictEntry(any(), any(), anyLong())).thenReturn(true)
//        timerWheel.nanos = clock
//        for (timeout in times) {
//            timerWheel.schedule(Timer(timeout))
//        }
//        timerWheel.advance(cache, duration)
//
//        var minDelay = Long.Companion.MAX_VALUE
//        var minSpan = Int.Companion.MAX_VALUE
//        var minBucket = Int.Companion.MAX_VALUE
//        for (i in timerWheel.wheel.indices) {
//            for (j in timerWheel.wheel[i].indices) {
//                val timers = getTimers(timerWheel.wheel[i][j])
//                for (delay in timers) {
//                    if (delay!! < minDelay) {
//                        minDelay = delay
//                        minBucket = j
//                        minSpan = i
//                    }
//                }
//            }
//        }
//
//        val delay = timerWheel.expirationDelay
//        if (minDelay == Long.Companion.MAX_VALUE) {
//            val format = "delay=%s but minDelay=%s, minSpan=%s, minBucket=%s"
//            assertWithMessage(format, delay, minDelay, minSpan, minBucket)
//                .that(delay).isEqualTo(Long.Companion.MAX_VALUE)
//            return
//        }
//
//        val maxError: Long = minDelay + SPANS[minSpan]
//        if (maxError > delay) {
//            val format = "delay=%s but minDelay=%s, minSpan=%s, minBucket=%s"
//            assertWithMessage(format, delay, minDelay, minSpan, minBucket)
//                .that(delay).isLessThan(maxError)
//        }
//    }

    //@DataProvider(name = "clock")
    fun providesClock(): MutableIterator<Any?> {
        return LongStream.of(*CLOCKS).mapToObj<Any?>(LongFunction { obj: Long -> Any::class.java.cast(obj) }).iterator()
    }

    //@DataProvider(name = "schedule")
    fun providesSchedule(): MutableIterator<Array<Long>> {
        val args = ArrayList<Array<Long>>()
        for (clock in CLOCKS) {
            args.add(arrayOf(clock, TimeUnit.SECONDS.toNanos(10), 0))
            args.add(arrayOf(clock, TimeUnit.MINUTES.toNanos(3), 2))
            args.add(arrayOf(clock, TimeUnit.MINUTES.toNanos(10), 3))
        }
        return args.iterator()
    }

    //@DataProvider(name = "fuzzySchedule")
    fun providesFuzzySchedule(): Array<Array<Any?>?> {
        val times = LongArray(5000)
        val clock = ThreadLocalRandom.current().nextLong()
        val bound: Long = clock + TimeUnit.DAYS.toNanos(1) + SPANS[SPANS.size - 1]
        for (i in times.indices) {
            times[i] = ThreadLocalRandom.current().nextLong(clock + 1, bound)
        }
        val duration = ThreadLocalRandom.current().nextLong(clock + 1, bound)
        return arrayOf<Array<Any?>?>(arrayOf<Any?>(clock, duration, times))
    }

    //@Test(dataProvider = "clock")
    fun reschedule(clock: Long) {
        val captor: ArgumentCaptor<Node<Long, Long>> = ArgumentCaptor.captor()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true)
        timerWheel.nanos = clock

        val timer = Timer(clock + TimeUnit.MINUTES.toNanos(15))
        timerWheel.schedule(timer)
        val startBucket = timer.nextInVariableOrder

        timer.variableTime = clock + TimeUnit.HOURS.toNanos(2)
        timerWheel.reschedule(timer)
        assertThat(timer.nextInVariableOrder).isNotSameInstanceAs(startBucket)

        timerWheel.advance(cache, clock + TimeUnit.DAYS.toNanos(1))
        checkEmpty(timerWheel)
    }

    //@Test(dataProvider = "clock")
    fun deschedule(clock: Long) {
        val timerWheel = TimerWheel<Long, Long>()

        val timer = Timer(clock + 100)
        timerWheel.nanos = clock
        timerWheel.schedule(timer)
        timerWheel.deschedule(timer)
        assertThat(timer.nextInVariableOrder).isNull()
        assertThat(timer.previousInVariableOrder).isNull()
    }

    //@Test(dataProvider = "clock")
    fun deschedule_notScheduled(clock: Long) {
        val timerWheel = TimerWheel<Long, Long>()

        timerWheel.nanos = clock
        timerWheel.deschedule(Timer(clock + 100))
    }

    //@Test(dataProvider = "fuzzySchedule")
    fun deschedule_fuzzy(clock: Long, nanos: Long, times: LongArray) {
        val timerWheel = TimerWheel<Long, Long>()
        val timers = ArrayList<Timer>()
        timerWheel.nanos = clock

        for (timeout in times) {
            val timer = Timer(timeout)
            timerWheel.schedule(timer)
            timers.add(timer)
        }
        for (timer in timers) {
            timerWheel.deschedule(timer)
        }
        checkTimerWheel(timerWheel, nanos)
    }

    //@Test(dataProvider = "clock")
    fun expire_reschedule(clock: Long) {
        val captor: ArgumentCaptor<Node<Long, Long>> = ArgumentCaptor.captor()
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        `when`(cache.evictEntry(captor.capture(), any(), anyLong())).then({ invocation ->
            val timer: Timer = invocation.getArgument(0)
            timer.variableTime = timerWheel.nanos + 100
            false
        })

        timerWheel.nanos = clock
        timerWheel.schedule(Timer(clock + 100))
        timerWheel.advance(cache, clock + SPANS[0])

        verify(cache).evictEntry(any(), any(), anyLong())
        assertThat(captor.getValue()?.nextInVariableOrder).isNotNull()
        assertThat(captor.getValue()?.previousInAccessOrder).isNotNull()
    }

    //@Test(dataProvider = "cascade")
    fun cascade(clock: Long, duration: Long, timeout: Long, span: Int) {
        val cache: BoundedAble<Long, Long> = Mockito.mock()
        val timerWheel = TimerWheel<Long, Long>()

        timerWheel.nanos = clock
        timerWheel.schedule(Timer(clock + timeout))
        timerWheel.advance(cache, clock + duration)

        var count = 0
        for (i in 0..span) {
            for (j in timerWheel.wheel[i].indices) {
                count += getTimers(timerWheel.wheel[i][j]).size
            }
        }
        assertThat(count).isEqualTo(1)
    }

    //@DataProvider(name = "cascade")
    fun providesCascade(): MutableIterator<Array<Any>?> {
        val args = ArrayList<Array<Any>?>()
        for (i in 1..<SPANS.size - 1) {
            val span: Long = SPANS[i]
            val timeout = ThreadLocalRandom.current().nextLong(span + 1, 2 * span)
            val duration = ThreadLocalRandom.current().nextLong(span + 1, timeout - 1)
            for (clock in CLOCKS) {
                args.add(arrayOf<Any>(clock, duration, timeout, i))
            }
        }
        return args.iterator()
    }

    //@Test(dataProvider = "iterator")
    fun iterator_hasNext(
        timerWheel: TimerWheel<Long, Long>, iterable: Iterable<Node<Long, Long>>,
    ) {
        var iterator = iterable.iterator()
        assertThat(iterator.hasNext()).isFalse()

        timerWheel.schedule(Timer(1))
        assertThat(iterator.hasNext()).isFalse()

        iterator = iterable.iterator()
        assertThat(iterator.hasNext()).isTrue()
        assertThat(iterator.hasNext()).isTrue()

        iterator.next()
        assertThat(iterator.hasNext()).isFalse()

        assertThrows(NoSuchElementException::class.java, { iterator.next() })
    }

    //@DataProvider(name = "iterator")
    fun providesIterators(): Array<Array<Any?>?> {
        val ascendingTimerWheel = TimerWheel<Long, Long>()
        val descendingTimerWheel = TimerWheel<Long, Long>()
        val ascending: Iterable<Node<Long, Long>?> = ascendingTimerWheel
        val descending: Iterable<Node<Long, Long>?> = Iterable { descendingTimerWheel.descendingIterator() }
        return arrayOf<Array<Any?>?>(
            arrayOf<Any?>(ascendingTimerWheel, ascending),
            arrayOf<Any?>(descendingTimerWheel, descending)
        )
    }

//    @Test(dataProvider = "clock")
//    fun iterator_fixed(clock: Long) {
//        val timerWheel = TimerWheel<Long, Long>()
//
//        timerWheel.nanos = clock
//        val input = IntStream.range(0, 21).mapToLong(IntToLongFunction { i: Int ->
//            val time: Long = clock + TimeUnit.SECONDS.toNanos(2L shl i)
//            timerWheel.schedule(Timer(time))
//            time
//        }).boxed().collect(toImmutableList())
//
//        val ascending: Unit /* TODO: class org.jetbrains.kotlin.nj2k.types.JKJavaNullPrimitiveType */? =
//            Streams.stream(timerWheel.iterator())
//                .limit(input.size() + 1).map(Node::key).collect(toImmutableList())
//        assertThat(ascending).containsExactlyElementsIn(input).inOrder()
//
//        val descending: Unit /* TODO: class org.jetbrains.kotlin.nj2k.types.JKJavaNullPrimitiveType */? =
//            Streams.stream(timerWheel.descendingIterator())
//                .limit(input.size() + 1).map(Node::key).collect(toImmutableList())
//        assertThat(descending).containsExactlyElementsIn(input.reverse()).inOrder()
//    }

//    @Test(invocationCount = 25)
//    fun iterator_random() {
//        val timerWheel = TimerWheel<Long, Long>()
//
//        val range = ThreadLocalRandom.current().nextInt(0, 1000)
//        timerWheel.nanos = ThreadLocalRandom.current().nextLong(
//            TimeUnit.MILLISECONDS.toNanos(500), TimeUnit.DAYS.toNanos(7)
//        )
//        val input: R? = IntStream.range(0, range).mapToLong(IntToLongFunction { i: Int ->
//            ThreadLocalRandom.current().nextLong(
//                TimeUnit.SECONDS.toNanos(1), TimeUnit.DAYS.toNanos(7)
//            )
//        }).boxed().sorted().collect(toImmutableList())
//        for (time in input) {
//            timerWheel.schedule(Timer(time))
//        }
//
//        val ascending: Unit /* TODO: class org.jetbrains.kotlin.nj2k.types.JKJavaNullPrimitiveType */? =
//            Streams.stream(timerWheel.iterator())
//                .limit(range + 1).map(Node::key).collect(toImmutableList())
//        assertThat(ascending).containsExactlyElementsIn(input)
//        assertThat(ascending).containsExactlyElementsIn(
//            snapshot(timerWheel,  /* ascending= */true)
//        ).inOrder()
//
//        val descending: Unit /* TODO: class org.jetbrains.kotlin.nj2k.types.JKJavaNullPrimitiveType */? =
//            Streams.stream(timerWheel.descendingIterator())
//                .limit(range + 1).map(Node::key).collect(toImmutableList())
//        assertThat(descending).containsExactlyElementsIn(
//            snapshot(timerWheel,  /* ascending= */false)
//        ).inOrder()
//        assertThat(descending).containsExactlyElementsIn(input)
//    }

    @Test
    fun sentinel_ignored() {
        val node = Sentinel<Any?, Any?>()
        node.setValue(Any(), null)
        node.retire()
        node.die()

        assertThat(node.key).isNull()
        assertThat(node.value).isNull()
        assertThat(node.containsValue(Any())).isFalse()
        assertThat(node.isAlive).isFalse()
        assertThat(node.isRetired).isFalse()
        assertThat(node.isDead).isFalse()
    }

    @Test
    fun sentinel_unsupported() {
        val node = Sentinel<Any?, Any?>()
        assertThrows(UnsupportedOperationException::class.java, node::keyReference::get)
        assertThrows(UnsupportedOperationException::class.java, node::valueReference::get)
    }


    private class Timer(accessTime: Long) : Node<Long, Long>() {
        @Nullable
        override var previousInVariableOrder: Node<Long, Long>? = null


        override var nextInVariableOrder: Node<Long, Long>? = null
        override var variableTime: Long = 0

        init {
            variableTime = accessTime
        }


        override val key: Long?
            get() = variableTime
        override val keyReference: Any?
            get() = null
        override val value: Long?
            get() = variableTime
        override val valueReference: Any?
            get() = null

        public override fun setValue(value: Long, referenceQueue: ReferenceQueue<Long>?) {}

        override fun containsValue(value: Long): Boolean = false

        override val isAlive: Boolean
            get() = true
        override val isRetired: Boolean
            get() = false
        override val isDead: Boolean
            get() = false

        public override fun retire() {}
        public override fun die() {}
    }

    companion object {
        private val CLOCKS = longArrayOf(
            Long.Companion.MIN_VALUE, -SPANS[0] + 1, 0L, 0xfffffffc0000000L,
            Long.Companion.MAX_VALUE - SPANS[0] + 1, Long.Companion.MAX_VALUE, ThreadLocalRandom.current().nextLong()
        )

        private fun checkTimerWheel(timerWheel: TimerWheel<Long, Long>, duration: Long) {
            for (i in timerWheel.wheel.indices) {
                for (j in timerWheel.wheel[i].indices) {
                    for (timer in getTimers(timerWheel.wheel[i][j])) {
                        if (timer!! <= duration) {
                            throw AssertionError(
                                String.format(
                                     "wheel[%s][%d] by %ss", i, j,
                                    TimeUnit.NANOSECONDS.toSeconds(duration - timer)
                                )
                            )
                        }
                    }
                }
            }
        }

        private fun getTimers(sentinel: Node<*, *>): MutableList<Long?> {
            val timers = ArrayList<Long?>()
            var node: Node<*, *>? = sentinel.nextInVariableOrder
            while (node !== sentinel) {
                timers.add(node!!.variableTime)
                node = node.nextInVariableOrder
            }
            return timers
        }

        private fun checkEmpty(timerWheel: TimerWheel<Long, Long>) {
            for (wheel in timerWheel.wheel) {
                for (sentinel in wheel) {
                    assertThat(sentinel.nextInVariableOrder).isSameInstanceAs(sentinel)
                    assertThat(sentinel.previousInVariableOrder).isSameInstanceAs(sentinel)
                }
            }
        }

        /** Returns a snapshot roughly ordered by the expiration time.  */
//        private fun snapshot(timerWheel: TimerWheel<Long, Long>, ascending: Boolean): MutableList<Long?> {
//            val snapshot = ArrayList<Long?>()
//            val startLevel = if (ascending) 0 else timerWheel.wheel.size - 1
//            val successor: Function<Node<*, *>, Node<*, *>> =
//                if (ascending) Node::nextInVariableOrder else Node::previousInVariableOrder
//            for (i in timerWheel.wheel.indices) {
//                val indexOffset = if (ascending) i else -i
//                val index = startLevel + indexOffset
//                val ticks = (timerWheel.nanos ushr SHIFT[index].toInt()).toInt()
//                val bucketMask = (timerWheel.wheel[index].size - 1)
//                val startBucket = (ticks and bucketMask) + (if (ascending) 1 else 0)
//                for (j in timerWheel.wheel[index].indices) {
//                    val bucketOffset = if (ascending) j else -j
//                    val sentinel: Node<Long, Long>? =
//                        timerWheel.wheel[index][(startBucket + bucketOffset) and bucketMask]
//                    var node: Unit /* TODO: class org.jetbrains.kotlin.nj2k.types.JKJavaNullPrimitiveType */? =
//                        successor.apply(sentinel)
//                    while (node != sentinel) {
//                        if ((node.getKey() != null) && (node.getValue() != null) && node.isAlive()) {
//                            snapshot.add(node.getVariableTime())
//                        }
//                        node = successor.apply(node)
//                    }
//                }
//            }
//            return snapshot
//        }

        fun printTimerWheel(timerWheel: TimerWheel<Long, Long>) {
            val builder = StringBuilder()
            for (i in timerWheel.wheel.indices) {
                val ticks = (timerWheel.nanos ushr SHIFT[i].toInt()).toInt()
                val bucketMask = (timerWheel.wheel[i].size - 1)
                val index = (ticks and bucketMask)
                val buckets: TreeMap<kotlin.String?, MutableList<Long?>?> =
                    TreeMap<kotlin.String?, MutableList<Long?>?>()
                for (j in timerWheel.wheel[i].indices) {
                    val events = ArrayList<Long?>()
                    var node = timerWheel.wheel[i][j].nextInVariableOrder
                    while (node !== timerWheel.wheel[i][j]) {
                        events.add(node!!.key)
                        node = node.nextInVariableOrder
                    }
                    if (j == index) {
                        buckets.put("*" + j, events)
                    } else if (!events.isEmpty()) {
                        buckets.put(j.toString(), events)
                    }
                }
                builder.append(" - Wheel #").append(i + 1).append(": ").append(buckets).append('\n')
            }
            System.err.printf("%nCurrent state:%n%s%n%n", builder.deleteCharAt(builder.length - 1))
        }
    }
}

internal fun Assert<Long>.isAtMost(maxValue: Long) = isLessThanOrEqualTo(maxValue)
